#pragma once
#include "vector3.h"
#include "constants.h"
#include "algebra.h"
#include <iostream>

namespace krryn{
namespace math{
	// Row-major 4x4 matrix.
	//
	// Row-major matrices store rows consecutively in memory. 
	// You can check this by looking at the way the content
	// is defined. And by looking at the position in the matrix
	// where the translation is stored.
	//
	// Our matrix looks like this (because of the values 
	// of TX, TY and TZ)
	// 
	// [[0 0 0 X]
	//  [0 0 0 Y]
	//  [0 0 0 Z]
	//  [0 0 0 0]]
	//
	// It's stored in memory as a float[16] which means 
	// that it's stored like this:
	//
	// [[0 0 0 X] [0 0 0 Y] [0 0 0 Z] [0 0 0 0]]
	//
	// Column-major matrices are the transpose of 
	// row-major matrices. This means that IF it were
	// a column major matrix, the memory layout would
	// have looked something like this:
	//
	// [[0 0 0 0] [0 0 0 0] [0 0 0 0] [X Y Z 0]]
	//
	// Side note:
	// 
	// This matrix does not have it's multiplication order specified!
	// The reason is simple: we have not overloaded any operators on
	// this type to avoid confusion. Use the concatenate() function
	// for matrix multiplication and use transform() to do vector
	// multiplication (transformation).
	class matrix{
	public:
		enum{
			TX = 3, 
			TY = 7, 
			TZ = 11, 
			TW = 15,
			D0 = 0,
			D1 = 5, 
			D2 = 10, 
			D3 = 15, 
		};

		matrix(){
			identity();
		}

		matrix(const matrix &a_Matrix){
			for(int i = 0; i < 16; i++)
				cell[i] = a_Matrix.cell[i];
		}

		float& operator[](int a_N){
			return cell[a_N];
		}

		void identity(){
			cell[1] = cell[2] = cell[TX] = cell[4] = cell[6] = cell[TY] =
				cell[8] = cell[9] = cell[TZ] = cell[12] = cell[13] = cell[14] = 0;
			cell[D0] = cell[D1] = cell[D2] = cell[TW] = 1;
		}

		void init(vector3f a_Pos, float a_RX, float a_RY, float a_RZ){
			matrix t;
			t.rotate_x(a_RZ);
			rotate_y(a_RY);
			concatenate(t);
			t.rotate_z(a_RX);
			concatenate(t);
			translate(a_Pos);
		}

		void rotate_x(float a_RX){
			float sx = math::sin(math::degree_to_radian(a_RX));
			float cx = math::cos(math::degree_to_radian(a_RX));
			identity();
			cell[5] = cx, cell[6] = sx, cell[9] = -sx, cell[10] = cx;
		}

		void rotate_y(float a_RY){
			float sy = math::sin(math::degree_to_radian(a_RY));
			float cy = math::cos(math::degree_to_radian(a_RY));
			identity();
			cell[0] = cy, cell[2] = -sy, cell[8] = sy, cell[10] = cy;
		}

		void rotate_z(float a_RZ){
			float sz = math::sin(math::degree_to_radian(a_RZ));
			float cz = math::cos(math::degree_to_radian(a_RZ));
			identity();
			cell[0] = cz, cell[1] = sz, cell[4] = -sz, cell[5] = cz;
		}

		void scale(vector3f a_Scale){
			matrix m;

			m.cell[D0] = a_Scale.m_X;
			m.cell[D1] = a_Scale.m_Y;
			m.cell[D2] = a_Scale.m_Z;

			concatenate(m);
		}

		void scale(float a_Scale){
			matrix m;

			m.cell[D0] = a_Scale;
			m.cell[D1] = a_Scale;
			m.cell[D2] = a_Scale;

			concatenate(m);
		}

		vector3f get_translation() const{
			return vector3f(cell[TX], cell[TY], cell[TZ]);
		}

		void translate(vector3f a_Pos){
			cell[TX] += a_Pos.m_X; cell[TY] += a_Pos.m_Y; cell[TZ] += a_Pos.m_Z;
		}

		void set_translation(vector3f a_Pos){
			cell[TX] = a_Pos.m_X; cell[TY] = a_Pos.m_Y; cell[TZ] = a_Pos.m_Z;
		}

		void concatenate(const matrix& m2){
			matrix res;
			int c;
			for(c = 0; c < 4; c++){
				for(int r = 0; r < 4; r++){
					res.cell[r * 4 + c] = 
						// dot product of a column in m2 and 
						// a row in this matrix
						cell[r * 4 + 0] * m2.cell[c + 0] +
						cell[r * 4 + 1] * m2.cell[c + 4] +
						cell[r * 4 + 2] * m2.cell[c + 8] +
						cell[r * 4 + 3] * m2.cell[c + 12];
				}
			}

			for(c = 0; c < 16; c++){
				cell[c] = res.cell[c];
			}
		}

		vector3f transform(const vector3f& v) const{
			float x = cell[0] * v.m_X + cell[1] * v.m_Y + cell[2] * v.m_Z + cell[3];
			float y = cell[4] * v.m_X + cell[5] * v.m_Y + cell[6] * v.m_Z + cell[7];
			float z = cell[8] * v.m_X + cell[9] * v.m_Y + cell[10] * v.m_Z + cell[11];
			return vector3f(x, y, z);
		}

		void invert(){
			matrix t;
			int h, i;
			float tx = -cell[3], ty = -cell[7], tz = -cell[11];

			for(h = 0; h < 3; h++){
				for(int v = 0; v < 3; v++){
					t.cell[h + v * 4] = cell[v + h * 4];
				}
			}

			for(i = 0; i < 11; i++){
				cell[i] = t.cell[i];
			}

			cell[3] = tx * cell[0] + ty * cell[1] + tz * cell[2];
			cell[7] = tx * cell[4] + ty * cell[5] + tz * cell[6];
			cell[11] = tx * cell[8] + ty * cell[9] + tz * cell[10];
		}

		matrix transpose() const{
			matrix m;

			m[0] = cell[0];	 m[4] = cell[1];  m[8]  = cell[2];  m[12]  = cell[3];
			m[1] = cell[4];	 m[5] = cell[5];  m[9]  = cell[6];  m[13]  = cell[7];
			m[2] = cell[8];	 m[6] = cell[9];  m[10] = cell[10]; m[14]  = cell[11];
			m[3] = cell[12]; m[7] = cell[13]; m[11] = cell[14]; m[15]  = cell[15];
			
			return m;
		}

		static matrix create_perspective_projection(float a_Near, float a_Far, float a_Width, float a_Height);
		float cell[16];
	};

	std::ostream& operator<<(std::ostream& a_Stream, matrix &a_Matrix);
}
}
